package com.zdxlz.fcmp.common.security.service;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.zdxlz.fcmp.common.core.constant.CommonConstants;
import com.zdxlz.fcmp.common.core.constant.enums.ResCode;
import com.zdxlz.fcmp.common.core.exception.BizException;
import com.zdxlz.fcmp.common.security.config.JwtConfig;
import com.zdxlz.fcmp.common.security.util.JwtTokenUtil;
import com.zdxlz.fcmp.upms.api.entity.SystemUser;
import com.zdxlz.fcmp.upms.api.mapper.SystemUserMapper;
import com.zdxlz.fcmp.upms.api.service.ISystemUserService;
import io.jsonwebtoken.Claims;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import java.util.Map;

import static com.zdxlz.fcmp.common.core.constant.CommonConstants.*;
import static com.zdxlz.fcmp.common.core.constant.enums.ResCode.*;


/**
 * 登录校验方法
 */
@Component
@Slf4j
public class SysLoginService {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private JwtConfig JwtConfig;

    @Autowired
    private RedisAuthServiceImpl redisAuthService;

    @Resource
    private SystemUserMapper systemUserMapper;

    @Autowired
    @Lazy
    private AuthenticationManager authenticationManager;

    @Autowired
    private ISystemUserService userService;

    private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();


    /**
     * 登录验证
     *
     * @param mobile   用户名
     * @param password 密码
     * @param code     验证码
     * @param uuid     唯一标识
     * @return 结果
     */
    public Map<String, Object> login(String mobile, String password, String code, String uuid) {
        try {
            // 登陆前，先通过【AuthenticationManager】进行用户身份认证
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(mobile, password);
            Authentication authenticate = authenticationManager.authenticate(authenticationToken);
            if (ObjectUtils.isEmpty(authenticate) || !authenticate.isAuthenticated()) {
                throw new BizException(ResCode.ERR_LOGIN_FAILED.getCode(), ResCode.ERR_LOGIN_FAILED.getReason());
            }
            Long version = redisAuthService.getUserVersion(mobile);
            String accessToken = jwtTokenUtil.generateAccessToken(mobile, version);
            String refreshToken = jwtTokenUtil.generateRefreshToken(mobile, version);
            SecurityContextHolder.getContext().setAuthentication(authenticate);

            // 存储token到redis
            redisAuthService.cacheAccessToken(mobile, accessToken, version);
            redisAuthService.cacheRefreshToken(refreshToken, version);
            boolean flag = updateCaptcha(mobile, Boolean.FALSE);
            log.info("用户【{}】登录成功，重置验证码结果：{}", mobile, flag);

            return Map.of(
                    CommonConstants.ACCESS_TOKEN, accessToken, CommonConstants.REFRESH_TOKEN,
                    refreshToken, CommonConstants.ACCESS_TOKEN_EXPIRATION,
                    JwtConfig.getAccessTokenExpiration()
            );
        } catch (Exception e) {
            boolean flag = updateCaptcha(mobile, Boolean.TRUE);
            log.error("登陆失败，登陆手机号【{}】，开启验证码标识：{}", mobile, flag);
            throw new BizException(ResCode.ERR_LOGIN_FAILED.getCode(), ResCode.ERR_LOGIN_FAILED.getReason() + "，登陆手机号|" + mobile);
        }
    }

    private boolean updateCaptcha(String mobile, Boolean value) {
        LambdaUpdateWrapper<SystemUser> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(SystemUser::getMobile, mobile)
                .set(SystemUser::getEnableCaptcha, value);
        return userService.update(null, updateWrapper);
    }

    public boolean logout(String authorization) {
        // 校验token是否有效
        verifyTokenIsValid(authorization);
        String accessToken = authorization.substring(7);

        String mobile = "";
        try {
            // 获取 SecurityContextHolder 中的认证对象
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

            // 检查 authentication 是否为有效用户
            if (authentication != null && authentication.isAuthenticated() && !(authentication instanceof AnonymousAuthenticationToken)) {
                // 确保 authentication 不是匿名用户之后再进行类型转换
                if (authentication instanceof UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) {
                    SystemUserLoginDetail sysUser = (SystemUserLoginDetail) usernamePasswordAuthenticationToken.getPrincipal();
                    mobile = sysUser.getMobile();
                    // 删除 Redis 中的值
                    redisAuthService.clearAccessToken(mobile, accessToken);
                }
            }
            // 清除 SecurityContextHolder 中的用户信息
            SecurityContextHolder.clearContext();
        } catch (Exception e) {
            // 记录详细的错误日志
            log.error("退出系统失败，用户: {}, 错误信息: {}", mobile, e.getMessage(), e);
            throw new BizException(ResCode.ERR_COMMON_UN_KNOW.getCode(), ResCode.ERR_COMMON_UN_KNOW.getReason());
        }

        // 记录成功日志
        log.info("【{}】退出系统", mobile);
        return true;
    }

    public Map<String, Object> refreshAccessToken(String authorization, String refreshToken) {
        if (StrUtil.isBlank(authorization)) {
            throw new BizException(ResCode.ERR_COMMON_LACK.getCode(), ResCode.ERR_COMMON_LACK.getReason());
        }

        if (!authorization.startsWith(CommonConstants.AUTH_TOKEN_STRAT)) {
            throw new BizException(ResCode.ERR_COMMON_PARAMS.getCode(), ResCode.ERR_COMMON_PARAMS.getReason());
        }

        if (jwtTokenUtil.isTokenExpired(refreshToken)) {
            throw new BizException(ResCode.ERR_AUTH_EXPIRE.getCode(), ResCode.ERR_AUTH_EXPIRE.getReason());
        }
        Claims claims = jwtTokenUtil.getClaims(refreshToken);
        String mobile = claims.getSubject();
        // 获取用户的版本号，用于判断是否需要刷新token
        Long redisVersion = redisAuthService.getUserVersion(mobile);

        Long version = claims.get(CommonConstants.VERSION, Long.class);

        if (null == version || !version.equals(redisVersion)) {
            throw new BizException(ResCode.ERR_AUTH_INVALID.getCode(), ResCode.ERR_AUTH_INVALID.getReason());
        }

        // 判断redis是否存在refresh token
        if (!redisAuthService.isExistRefreshToken(refreshToken)) {
            throw new BizException(ResCode.ERR_AUTH_EXPIRE.getCode(), ResCode.ERR_AUTH_EXPIRE.getReason());
        }

        Long currentVersion = redisAuthService.getUserVersion(mobile);
        // 生成新的 Access Token
        String newAccessToken = jwtTokenUtil.generateAccessToken(mobile, currentVersion);
        String newRefreshToken = jwtTokenUtil.generateRefreshToken(mobile, currentVersion);
        String accessToken = authorization.substring(7);
        //删除原来的accessToken 和 refreshToken
        redisAuthService.clearAccessToken(mobile, accessToken);
        redisAuthService.clearRefreshToken(refreshToken);
        //添加新的accessToken 和 refreshToken
        redisAuthService.cacheAccessToken(mobile, newAccessToken, currentVersion);
        redisAuthService.cacheRefreshToken(newRefreshToken, currentVersion);

        return Map.of(
                CommonConstants.ACCESS_TOKEN, newAccessToken,
                CommonConstants.REFRESH_TOKEN, newRefreshToken,
                CommonConstants.ACCESS_TOKEN_EXPIRATION, JwtConfig.getAccessTokenExpiration()
        );
    }


    /**
     * @description：通过当前用户登陆账号，修改当前用户密码
     * @author：aman
     * @date：2025/2/25 17:03
     */
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateUserPassword(String authorization, String mobile, Map<String, String> map) {
        // 校验token是否有效
        verifyTokenIsValid(authorization);
        // 校验密码是否有效
        verifyPasswordIsValid(map);
        try {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            // 检查 authentication 是否为有效用户
            if (authentication != null && authentication.isAuthenticated() && !(authentication instanceof AnonymousAuthenticationToken)) {
                // 确保 authentication 不是匿名用户之后再进行类型转换
                if (authentication instanceof UsernamePasswordAuthenticationToken authenticationToken) {
                    SystemUserLoginDetail sysUser = (SystemUserLoginDetail) authenticationToken.getPrincipal();
                    SystemUser user = systemUserMapper.selectOne(new LambdaQueryWrapper<SystemUser>().eq(SystemUser::getMobile, mobile)
                                                                                                     .eq(SystemUser::getPassword, sysUser.getPassword()));
                    if (ObjectUtil.isEmpty(user)) throw new BizException(ResCode.ERROR_USER_NOT_EXIST.getCode(), ResCode.ERROR_USER_NOT_EXIST.getReason());
                    if (!ENCODER.matches(map.get(OLD_PASSWORD), user.getPassword())) {
                        throw new BizException(ResCode.RESETPASSWORD__OLD_PASSWORD_ERROR.getCode(), ResCode.RESETPASSWORD__OLD_PASSWORD_ERROR.getReason());
                    }
                    user.setPassword(ENCODER.encode(map.get(NEW_PASSWORD)));
                    systemUserMapper.updateById(user);

                    // 删除 Redis 中的值
                    redisAuthService.clearAccessToken(mobile, authorization.substring(7));
                    // 清除 SecurityContextHolder 中的用户信息
                    SecurityContextHolder.clearContext();
                }
            }
        } catch (Exception e) {
            // 记录详细的错误日志
            log.error("重置密码发生异常，用户账号: {}, 错误信息: {}", mobile, e.getMessage(), e);
            throw new BizException(ResCode.ERR_COMMON_UN_KNOW.getCode(), ResCode.ERR_COMMON_UN_KNOW.getReason());
        }
        return true;
    }

    /**
     * @description：校验token是否有效
     * @author：aman
     * @date：2025/2/27 10:21
     */
    public static void verifyTokenIsValid(String authorization) {
        // 检查 authorization 是否为空或空白
        if (StrUtil.isBlank(authorization)) {
            throw new BizException(ResCode.ERR_COMMON_PARAMS.getCode(), ResCode.ERR_COMMON_PARAMS.getReason());
        }

        if (!authorization.startsWith(CommonConstants.AUTH_TOKEN_STRAT)) {
            throw new BizException(ResCode.ERR_COMMON_PARAMS.getCode(), ResCode.ERR_COMMON_PARAMS.getReason());
        }
    }

    /**
     * @description：校验密码是否有效
     * @author：aman
     * @date：2025/2/27 10:21
     */
    public static void verifyPasswordIsValid(Map<String, String> map) {
        String oldPassword = map.get(OLD_PASSWORD);
        String newPassword = map.get(NEW_PASSWORD);
        String confirmPassword = map.get(CONFIRM_PASSWORD);
        if (StrUtil.isBlank(oldPassword) || StrUtil.isBlank(newPassword) || StrUtil.isBlank(confirmPassword))
            throw new BizException(ResCode.RESETPASSWORD_PASSWORD_NOT_EMPTY.getCode(), ResCode.RESETPASSWORD_PASSWORD_NOT_EMPTY.getReason());
        if (oldPassword.equals(newPassword))
            throw new BizException(RESETPASSWORD_NEWPASSWORD_OLDPASSWORD_NOT_EQUAL.getCode(), RESETPASSWORD_NEWPASSWORD_OLDPASSWORD_NOT_EQUAL.getReason());
        if (!newPassword.equals(confirmPassword))
            throw new BizException(RESETPASSWORD_NEWPASSWORD_CONFIRMPASSWORD_NOT_EQUAL.getCode(), RESETPASSWORD_NEWPASSWORD_CONFIRMPASSWORD_NOT_EQUAL.getReason());
    }
}
